ClassLoader
在启动Activity
的时候会调用loadClass
方法,我们就从这里入手:
1 | public Activity newActivity(ClassLoader cl, String className, |
然后我们点击进入进入了ClassLoade
r的loadClass
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
看到源码是调用了findClass
方法:1
2
3protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
发现这个是抛出了一个异常,就没办法继续阅读了。这个时候发现ClassLoader是一个抽象类,应该是子类重写了这个方法,然后通过启动StartActivity
的源码可以得到:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53public ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent) {
/*
* This is the parent we use if they pass "null" in. In theory
* this should be the "system" class loader; in practice we
* don't use that and can happily (and more efficiently) use the
* bootstrap class loader.
*/
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}
/*
* If we're one step up from the base class loader, find
* something in our cache. Otherwise, we create a whole
* new ClassLoader for the zip archive.
*/
if (parent == baseParent) {
ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
zip,
librarySearchPath,
libraryPermittedPath,
parent,
targetSdkVersion,
isBundled);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
setupVulkanLayerPath(pathClassloader, librarySearchPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
}
}
得知这个ClassLoader
是PathClassLoader
,点进去查看1
2
3
4
5
6
7
8
9
10
11public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
发现其实是BaseDexClassLoader里面的findClass()生效了,我们通过http://androidxref.com/ 查看源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
其实就是DexPathList pathList
这个字段得到Class<?>
,我们继续查看DexPathList
里面源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
/**
* * Finds the named class in one of the dex files pointed at by
* * this instance. This will find the one in the earliest listed
* * path element. If the class is found but has not yet been
* * defined, then this method will define it in the defining
* * context that this instance was constructed with.
* *
* * @param name of class to find
* * @param suppressed exceptions encountered whilst finding the class
* * @return the named class or {@code null} if the class is not
* * found in any of the dex files
*
*/
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
可以发现其实是一个dex
的Element[]
数组,通过for
循环得到相应的值,所以我们就可以把改变的class
打成dex
格式的文件,通过反射把这个dex
文件里面的Element[] dexElements
值插入到原APP的Element[] dexElements
前就可以了。
下面是具体代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171package com.zzw.baselibray.fixBug;
import android.content.Context;
import android.util.Log;
import com.zzw.baselibray.util.FileUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import dalvik.system.BaseDexClassLoader;
/**
* Created by zzw on 2017/5/5.
* 热修复管理
*/
public class FixBugManager {
private static final String TAG = "FixBugManager";
private Context mContext;
private File mDexDir;//应用可以访问的dex目录
public FixBugManager(Context context) {
this.mContext = context;
//获取到应用可以访问的dex目录
this.mDexDir = context.getDir("odex", Context.MODE_PRIVATE);
}
/**
* 设置新的dexElements到applicationClassLoader里面
*
* @param classLoader
* @param dexElements
*/
private void setElementsToClassLoader(ClassLoader classLoader, Object dexElements) throws NoSuchFieldException, IllegalAccessException {
//1.先获取ClassLoader里面的pathList
Field pathListFiled = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathList = pathListFiled.get(classLoader);
//2.获取pathList里面的dexElements字段并设置新的值
Field dexElementsField = pathList.getClass().getField("dexElements");
dexElementsField.setAccessible(true);
dexElementsField.set(pathList, dexElements);
}
/**
* 从ClassLoader里面获取dexElements
*
* @param applicationClassLoader
* @return
*/
private Object getElementsByClassLoader(ClassLoader applicationClassLoader) throws NoSuchFieldException, IllegalAccessException {
//1.先获取ClassLoader里面的pathList
Field pathListFiled = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListFiled.setAccessible(true);
Object pathList = pathListFiled.get(applicationClassLoader);
//2.获取pathList里面的dexElements
Field dexElementsField = pathList.getClass().getField("dexElements");
dexElementsField.setAccessible(true);
Object dexElements = dexElementsField.get(pathList);
return dexElements;
}
/**
* 合并两个dexElements数组
*
* @param arrayLhs
* @param arrayRhs
* @return
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
/**
* 加载所有的修复包
*/
public void loadFixDex() throws Exception {
File[] files = mDexDir.listFiles();
List<File> fixDexFiles = new ArrayList<>();
for (File dexFile : files) {
if (dexFile.getName().endsWith(".dex")) {
fixDexFiles.add(dexFile);
}
}
fixDexFiles(fixDexFiles);
}
/**
* 修复dex包
*
* @param fixDexPath dexDex修复路径
*/
public void fixDex(String fixDexPath) throws Exception {
File srcFile = new File(fixDexPath);
if (!srcFile.exists()) {
throw new FileNotFoundException(fixDexPath);
}
File destFile = new File(mDexDir, srcFile.getName());
if (destFile.exists()) {
Log.d(TAG, "patch [" + fixDexPath + "] has be loaded.");
return;
}
FileUtil.copyFile(srcFile, destFile);// copy to patch's directory
// FileUtil.deleteFile(srcFile);//copy完成后删除
//2.2 ClassLoader读取fixDex路径 为什么加入到集合?-->可能已启动就可能要修复
List<File> fixDexFiles = new ArrayList<>();
fixDexFiles.add(destFile);
fixDexFiles(fixDexFiles);
}
/**
* 修复dex 已经修复过的dex文件全部copy在mContext里面,application初始化的时候将这些多个dex文件一起修复
*
* @param fixDexFiles
*/
private void fixDexFiles(List<File> fixDexFiles) throws Exception {
//1.先获取applicationClassLoader的pathList字段的dexElements值
ClassLoader applicationClassLoader = mContext.getClassLoader();
Object applicationDexElements = getElementsByClassLoader(applicationClassLoader);
//2.获取下载好的补丁的dexElements
//2.1 移动到系统能够访问的dex目录下 --> ClassLoader
File optimizedDirectory = new File(mDexDir, "oder");
if (!optimizedDirectory.exists())
optimizedDirectory.mkdirs();
//修复
for (File fixDexFile : fixDexFiles) {
//dexPath 加载的dex路径
//optimizedDirectory 解压路径
//librarySearchPath so文件位置
//parent 父ClassLoader
ClassLoader fixClassLoader = new BaseDexClassLoader(
fixDexFile.getAbsolutePath(), //dexPath 加载的dex路径
optimizedDirectory,// 解压文件
null,
applicationClassLoader);
Object fixDexElements = getElementsByClassLoader(fixClassLoader);
//3.把补丁的dexElements插到已经已经运行的dexElements前面
//合并完成 fixDexElements插入dexElements之前
applicationDexElements = combineArray(fixDexElements, applicationDexElements);
//把合并的数组注入到原来的applicationClassLoader类中
setElementsToClassLoader(applicationClassLoader, applicationDexElements);
}
}
}
1 | /* |
学习来源:红橙Darren